
/**
 * 
 * 这段代码是用于处理媒体设备（如摄像头和麦克风）的JavaScript模块。它提供了一些功能，如拍照，录音，以及处理视频流和音频流。
 * 以下是一些主要功能的简要说明：
 * - createMediaVideo(): 创建一个视频元素，并将其添加到DOM中。
 * - drawImage(): 从视频流中获取图像，可能进行裁剪和压缩，并将结果显示在目标元素上。
 * - getUserMedia(constrains): 获取用户的媒体设备，如摄像头或麦克风。这是一个兼容性函数，可以在不同的浏览器环境中工作。
 * - updateOptions(options): 更新模块的配置选项。
 * - openMedia(constrains): 打开用户的媒体设备，并返回一个Promise。
 * - closeMedia(): 关闭所有打开的媒体设备。
 * - startTakePhoto(options): 开始使用后置摄像头拍照。
 * - startTakeFace(options): 开始使用前置摄像头拍照。
 * - openAudio(): 打开音频设备。
 * - startAudioRecord(options): 开始录音。
 * - stopAudioRecord(): 停止录音。
 * 
 * 这个模块还导出了一个对象，其中包含了所有的函数和一些额外的工具函数。
 */

import config from '@/config';
import utils from '@/utils';
import { blobToDataUrl, dataURLtoFile, compressImg } from '@/compress'
import inputfile from './inputfile';

// 渲染图片的目标dom id
let targetid = '';
// 温馨提示信息
let tips = '';
// 接受的文件类型
let accept = '';
// 调用设备, user-前置摄像头
let capture = '';
// 是否启用旋转
let needRotate = false;
// 默认的orientation
let defaultOri = null;
// 是否展示引导框
let withGuide = false;
let cropByGuide = false;
// 是否打开扫描效果
let scanEffect = false;
// 是否支持选择文件
let supportFile = false;
let fileBtnText = '选择文件';
// 是否需要压缩，基准文件大小是500kb，加入拍摄所得图片文件小于500kb，不会进行压缩
let needCompress = false;
// 默认开启压缩的基准大小
let baseSize = 1024 * 50;
// 引导框大小及定位
let guideX = 0;
let guideY = 0;
let guideWidth = 0;
let guideHeight = 0;

// 总的容器
let mediaBlockDom = null;
// video 容器
let videoDom = null;
// 引导框
let guideDom = null;
// 扫描效果
let scannerbox = null;
// 视频流video
let video = null;
// 返回按钮
let backBtn = null;
// 拍照按钮
let takeBtn = null;
// 选择文件按钮
let fileBtn = null;
// 提示标签
let tipsDom = null;


// 渲染图片canvas,点击拍照时需要将视频流渲染到canvas保存成图片
let canvas = null;
// 视频流
let videoStream = null;
// 音频采集器
let mediaRecorder = null;
// 音频数据集
let audioChunks = [];

// 成功回调
let success = null;
// 失败回调
let fail = null;
let onclose = null;

function createMediaVideo(videoSetting) {
    
    videoSetting = videoSetting? videoSetting : {height: config.videoHeight, width: config.videoWidth};

    if (!mediaBlockDom) {
        mediaBlockDom = document.createElement("DIV");
        mediaBlockDom.id = "media-block";
        mediaBlockDom.style.cssText = config.mediaBlockCss;

        // 组件样式
        const style = document.createElement("style");
        style.type = 'text/css';
        style.innerHTML = config.inlineStyle;

        mediaBlockDom.appendChild(style);

        // 视频流播放video
        if (!video) {
            videoDom = document.createElement("DIV");
            videoDom.id = 'videowrapper';
            videoDom.innerHTML = config.videoTpl(videoSetting);
            video = videoDom.querySelector("#mediavideo");
            video.setAttribute('playsinline', '');
        }

        let bottomBlock = document.createElement("DIV");
        bottomBlock.className = "bottom-block";

        tipsDom = document.createElement("DIV");
        tipsDom.className = "xmedia-tips";

        bottomBlock.appendChild(tipsDom);

        let bottomBtns = document.createElement("DIV");
        bottomBtns.className = 'bottom-btns';
        let bbFirstDiv = document.createElement("DIV");
        let bbMidDiv = document.createElement("DIV");
        let bbLastDiv = document.createElement("DIV");

        // 返回按钮
        if (!backBtn) {
            backBtn = document.createElement("button");
            backBtn.id = "xmedia-back-btn";
            backBtn.className = "xmedia-back-btn";
            backBtn.innerHTML = '取消'
            backBtn.addEventListener('click', function () {
                closeMedia();
            })
            bbFirstDiv.appendChild(backBtn);
        }

        // 拍摄按钮
        if (!takeBtn) {
            takeBtn = document.createElement("button");
            takeBtn.className = "xmedia-take-btn";
            takeBtn.innerHTML = "<span></span>";
            takeBtn.addEventListener('click', function () {
                drawImage();
            })
            bbMidDiv.appendChild(takeBtn);
        }

        // 拍摄按钮
        if (!fileBtn) {
            fileBtn = document.createElement("button");
            fileBtn.id = "xmedia-file-btn";
            fileBtn.className = "xmedia-file-btn";
            fileBtn.innerHTML = fileBtnText;
            fileBtn.addEventListener('click', function () {
                closeMedia();
                inputfile.inputFile(accept, capture, function (blob) {
                    const fileName = 'tmp_' + new Date().getTime() + '.png';
                    blobToDataUrl(blob, fileName).then(b64 => {
                        resolveData(blob, b64, blob)
                    })                   
                }, baseSize, needRotate);
            })
            bbLastDiv.appendChild(fileBtn);
        }

        bottomBtns.appendChild(bbFirstDiv);
        bottomBtns.appendChild(bbMidDiv);
        bottomBtns.appendChild(bbLastDiv);
        bottomBlock.appendChild(bottomBtns);

        mediaBlockDom.appendChild(video);
        mediaBlockDom.appendChild(bottomBlock);
        video.volume = 0;
        video.muted = false;
        video.setAttribute('playsinline', '');
    }

    // 引导框设置
    if (withGuide) {
        if(!guideDom){
            guideDom = document.createElement("DIV");
            guideDom.id = 'media-guide';
            guideDom.style.cssText = config.guideCss;
        }
        
        if(scanEffect){
            guideDom.className = 'scanner-box';
            scannerbox = document.createElement("DIV");
            scannerbox.className = 'box';
            scannerbox.innerHTML = '<div class="line"></div>';
            guideDom.appendChild(scannerbox);
        }else{
            scannerbox && scannerbox.remove();
        }

        // 先删除原引导框
        if(mediaBlockDom.querySelector(".xmedia-guide-dom")){
            mediaBlockDom.querySelector(".xmedia-guide-dom").remove();
        }

        guideDom.className = guideDom.className ? guideDom.className + ' xmedia-guide-dom': 'xmedia-guide-dom';
        mediaBlockDom.appendChild(guideDom)
    }

    // 填充提示信息
    tips && (tipsDom.innerHTML = tips);

    // 先隐藏引导框与拍照按钮
    guideDom && (guideDom.style.display = "none");    
    tipsDom.style.display = "none";
    fileBtn.style.display = "none";
    takeBtn.style.display = "none";

    document.body.appendChild(mediaBlockDom);
    return video;
}

/**
 * 获取视频流并生成图片
 * @param {*} closeStream - 用于渲染图片的目标dom id
 */
export function drawImage(closeInEnd) {

    if(!video){
        throw new Error("video还未初始化，请先调用startTakePhoto/startTakeFace api")
    }

    try {
        if (!canvas) {
            canvas = document.createElement("canvas");
            canvas.id = "videocanvas";
            canvas.style.position = "absolute";
            canvas.style.top = "-10000000px";
            canvas = canvas
        }

        // 展示canvas
        canvas.style.display = 'block';

        const { yRatio, xRatio } = utils.getXYRatio(video);

        // 获取裁切框的位置
        if (withGuide && guideDom && cropByGuide && !guideWidth) {
            const { left, top, width, height } = guideDom.getBoundingClientRect();
            guideX = left;
            guideY = top;
            guideWidth = width;
            guideHeight = height;
        }

        const { videoHeight: vh, videoWidth: vw, offsetHeight: oh, offsetWidth: ow } = video;
        let { tleft, ttop, twidth, theight } = withGuide && guideDom && cropByGuide ? { tleft: guideX, ttop: guideY, twidth: guideWidth, theight: guideHeight } : { tleft: 0, ttop: 0, twidth: ow, theight: oh };
        
        const context = canvas.getContext('2d');
        const dWidth = xRatio(twidth);
        const dHeight = yRatio(theight);
        canvas.width = dWidth;
        canvas.height = dHeight;
        context.drawImage(
            video,
            xRatio(tleft + window.scrollX),
            yRatio(ttop + window.scrollY),
            dWidth,
            dHeight,
            0,
            0,
            dWidth,
            dHeight
        );

        // 获取当前截图的base64编码
        let base64 = canvas.toDataURL('image/jpeg');
        let file = dataURLtoFile(base64);

        // 清除canvas内容，并隐藏
        canvas.width = 0;
        canvas.height = 0;
        canvas.display = 'none';
        
        // 如果没有明确设置不关闭媒体设备，默认均进行关闭操作
        if(closeInEnd !== false){
            closeMedia(); 
        }      

        if (needCompress) {
            compressImg(file, baseSize, needRotate, defaultOri).then(function (blob) {
                resolveData(file, base64, blob);
            }).catch(function (err) {
                targetid && (document.querySelector(targetid).src = base64);
                typeof success === 'function' && success({ base64, file });
            })
        } else {
            targetid && (document.querySelector(targetid).src = base64);
            typeof success === 'function' && success({ base64, file });
        }

    } catch (error) {
        closeMedia();
        typeof fail === 'function' && fail(error);
    }

}

/**
 * 返回拍摄的数据
 * @param {*} file 
 * @param {*} base64 
 * @param {*} blob 
 */
function resolveData(file, base64, blob){
    const fileName = 'tmp_' + new Date().getTime() + '.png';
    blobToDataUrl(blob, fileName).then(b64 => {
        base64 = b64

        file = dataURLtoFile(base64);
        targetid && (document.querySelector(targetid).src = base64);
        typeof success === 'function' && success({ base64, file });
    }).catch(err => {
        targetid && (document.querySelector(targetid).src = base64);
        typeof success === 'function' && success({ base64, file });
    });
}

/**
 * 访问用户媒体设备的兼容方法
 * @param {*} constrains 
 * @returns 
 */
export function getUserMedia(constrains) {
    if (navigator.mediaDevices?.getUserMedia) {
        //最新标准API
        return navigator.mediaDevices.getUserMedia(constrains);
    } else if (navigator.webkitGetUserMedia) {
        //webkit内核浏览器
        return navigator.webkitGetUserMedia(constrains);
    } else if (navigator.mozGetUserMedia) {
        //Firefox浏览器
        return navigator.mozGetUserMedia(constrains);
    } else if (navigator.getUserMedia) {
        //旧版API
        return navigator.getUserMedia(constrains);
    }
}

/**
 * 根据入参更新各项参数
 * @param {*} options 
 */
function updateOptions(options) {
    targetid = options.targetId;
    capture = options.capture || '';
    tips = options.tips;
    needRotate = options.needRotate;
    defaultOri = options.defaultOri;
    withGuide = options.withGuide;
    cropByGuide = options?.cropByGuide || false;
    scanEffect = options.scanEffect;
    guideDom = options.guideDom || guideDom;
    supportFile = options.supportFile;
    fileBtnText = options.fileBtnText || fileBtnText;
    needCompress = options.needCompress;
    if (options.needCompress && !!options.baseSize && isNaN(options.baseSize)) {
        baseSize = options.baseSize;
    }
    success = options.success;
    onclose = options.onclose;
    fail = options.fail;

    console.info(options)
}

/**
 * 打开媒体设备
 * @param {*} constrains 
 * @returns 
 */
export function openMedia(constrains) {

    return new Promise((resolve, reject) => {

        if (!constrains.audio && !constrains.video) {
            return reject();
        }

        function renderStream(stream) {
            
            try {
                const videoTrack = stream.getVideoTracks()[0];
                // 获取摄像头的设置
                const settings = videoTrack.getSettings();

                // 计算video宽高
                const videoSetting = utils.getVideoWH(settings);

                let mediaVideo = createMediaVideo(videoSetting);

                if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
                    //引入第一路视频流，不引入音频流
                    let mediaStream = new MediaStream()
                    mediaStream.addTrack(stream.getVideoTracks()[0])
                    mediaVideo.srcObject = mediaStream
                    video.play();
                }
                else {
                    try {
                        mediaVideo.src = URL.createObjectURL(stream)
                    }catch(e){
                        mediaVideo.srcObject = stream;
                    }
                }

                mediaVideo.onloadedmetadata = function () {
                    try {
                        mediaVideo.play();

                        // if(mediaVideo.currentTime <= 0 && navigator.userAgent.indexOf('OpenHarmony') == -1){
                        //     // 如果视频流无法正常播放
                        //     return reject("视频流无法正常播放");
                        // }

                        // 视频流播放后再展示引导框与拍照按钮
                        // 根据配置开启引导框
                        withGuide && guideDom && (guideDom.style.display = "block");
                        // 根据配置开启选择文件
                        supportFile && (fileBtn.style.display = "block");
                        // 展示提示信息
                        tips && (tipsDom.style.display = "block");
                        // 开启拍照按钮
                        takeBtn.style.display = "block";
                        mediaBlockDom.style.display = "block";
                        resolve();                    
                    }catch(e) {
                        reject()
                    }                    
                }
                if (utils.isiOS && utils.isWechat) {
                    // ios微信需要主动调起播放才能展示视频流内容
                    mediaVideo.play();
                }
            }catch(err){
                reject(err)
            }            
        }

        getUserMedia(constrains).then((result) => {
            videoStream = result;
            if (!constrains.video && constrains.audio) {
                // 录音直接返回媒体流
                return resolve(result);
            } else {
                return renderStream(result);
            }
        }).catch((err) => {
            reject(`访问媒体设备失败：${err.name}-${err.message}`)
        });
    })
}

/**
 * 关闭媒体设备
 */
export function closeMedia() {

    mediaBlockDom && (mediaBlockDom.style.display = "none");

    if (videoStream && videoStream.getTracks()) {
        videoStream.getTracks().forEach(function (track) {
            track.stop();
        });
    }

    typeof onclose === 'function' && onclose();
}



/**
 * 调用后置摄像头拍摄
 * @param {*} options 
 * @returns 
 */
export function startTakePhoto(options) {
    accept = 'image';
    updateOptions(options);
    return openMedia(utils.getVideoConstrains('environment')).then(() => {
        console.info('可正常使用媒体设备...')
    })
}

/**
 * 调用前置摄像头拍照
 * @param {*} options 
 * @returns 
 */
export function startTakeFace(options) {
    accept = 'image';
    updateOptions(options);
    return openMedia(utils.getVideoConstrains('user')).then(() => {
        console.info('可正常使用媒体设备...')
    })
}

export function openAudio() {
    return new Promise((resolve, reject) => {
        let constrains = { audio: true, video: false };
        openMedia(constrains).then((stream) => {
            closeMedia();
            resolve();
        }).catch((e) => { reject(e) })
    })
}

export function startAudioRecord(options) {

    if (mediaRecorder && state === "recording") {
        return false;
    }

    let constrains = utils.getAudioConstrains();
    success = options.success;
    fail = options.fail;

    return openMedia(constrains).then((stream) => {

        // 创建 MediaRecorder 实例 
        mediaRecorder = new MediaRecorder(stream);

        // 注册数据可用事件,以获取编码后的媒体数据块
        mediaRecorder.ondataavailable = event => {
            audioChunks.push(event.data);
        }

        // 开始录制
        mediaRecorder.start();

        // 停止录音后获取数据并重置recorder和数据集及关闭设备引用
        mediaRecorder.onstop = function () {
            closeMedia();
            typeof success === 'function' && success(new Blob(audioChunks, {
                type: 'audio/mp3'
            }));

            mediaRecorder = null;
            audioChunks = [];
        }
    })
}

export function stopAudioRecord() {
    mediaRecorder.stop();
}

// 监听页面隐藏或退出，并关闭视频流
document.addEventListener('visibilitychange', function(){
    // 用户离开了当前页面
    if (document.visibilityState === 'hidden') {
        console.info('用户离开页面')
        closeMedia();
    }
})
window.addEventListener('beforeunload', function (e) {
    console.info('页面beforeunload')
    closeMedia();
});

export default {
    supportUserMedia: navigator.mediaDevices?.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.getUserMedia,
    getUserMedia,
    openMedia,
    closeMedia,
    startTakePhoto,
    startTakeFace,
    openAudio,
    startAudioRecord,
    stopAudioRecord,
    capture: drawImage,
    compressImg,
    blobToDataUrl,
    dataURLtoFile,
    ...utils,
    ...inputfile
}